package main

import (
	"context"
	"fmt"
	"time"

	"go.etcd.io/etcd/clientv3"
)

func main() {

	var (
		config         clientv3.Config
		client         *clientv3.Client
		err            error
		lease          clientv3.Lease
		leaseGrantResp *clientv3.LeaseGrantResponse
		leaseid        clientv3.LeaseID
		keepRespChan   <-chan *clientv3.LeaseKeepAliveResponse
		keepResp       *clientv3.LeaseKeepAliveResponse
		ctx            context.Context
		cancelFunc     context.CancelFunc
		kv             clientv3.KV
		txn            clientv3.Txn
		txnResp        *clientv3.TxnResponse
	)

	// 客户端配置
	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"}, // 集群列表
		DialTimeout: 5 * time.Second,
	}

	// 建立一个客户端
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}

	// lease实现锁自动过期
	// op操作
	// txn事务：if else then

	// 1.上锁 (创建租约，自动续租，拿着租约去抢占一个key)
	// 申请一个lease (租约)
	lease = clientv3.NewLease(client)

	// 申请一个5秒的租约
	if leaseGrantResp, err = lease.Grant(context.TODO(), 5); err != nil { // ttl值：过期时间
		fmt.Println(err)
		return
	}

	// 拿到租约的ID
	leaseid = leaseGrantResp.ID

	// 准备一个用于取消自动续租的context
	ctx, cancelFunc = context.WithCancel(context.TODO())

	// 租约的生命期是：自动续租时间 + 停止自动续租后的自带租约时间

	// 确保函数退出后，自动续租会停止
	defer cancelFunc()                          // 中断自动续租的协程
	defer lease.Revoke(context.TODO(), leaseid) // 释放租约

	// 自动续租
	if keepRespChan, err = lease.KeepAlive(ctx, leaseid); err != nil {
		fmt.Println(err)
		return
	}

	// 处理续租应答的协程
	go func() {
		for {
			select {
			case keepResp = <-keepRespChan: // 每次心跳的应答请求
				if keepRespChan == nil { // 续租异常，应答会返回一个nil (判断keepResp效果相同)
					fmt.Println("租约已经失效了")
					goto END // 跳到指定的标签位置 (一般是跳出循环)
				} else { // 每秒会续租一次，所以会收到一次应答
					fmt.Println("收到自动续租应答： ", keepResp.ID)
				}
			}
		}
	END:
	}()

	// if不存在key，then设置它，else抢锁失败
	kv = clientv3.NewKV(client)

	// 创建事务
	txn = kv.Txn(context.TODO())

	// 定义事务

	// 如果key不存在		WithLease 防止宕机后锁被永远占住
	txn.If(clientv3.Compare(clientv3.CreateRevision("/cron/lock/job9"), "=", 0)).
		Then(clientv3.OpPut("/cron/lock/job9", "xxx", clientv3.WithLease(leaseid))).
		Else(clientv3.OpGet("/cron/lock/job9")) // 否则抢锁失败

	// 提交事务
	if txnResp, err = txn.Commit(); err != nil {
		fmt.Println(err)
		return // 没有问题
	}

	// 判断是否抢到了锁
	if !txnResp.Succeeded {
		fmt.Println("锁被占用", string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
		return
	}
	// txnResp.Responses指的是响应的OpResponse，[0]这里是Else里面的第一个Operation
	// OpGet返回的是GetResponseRange，kvs是返回的value集

	// 2.处理事务

	fmt.Println("处理事务")
	time.Sleep(10 * time.Second)

	// 3.释放锁 (取消自动续租，释放租约) 租约释放，与租约关联的key就会自动删除
	// defer 会把租约释放掉，关联的KV就被删除了
}
